/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019.
 * Description: add reboot driver for hisi soc
 * Author: zhangwen
 * Create: 2019-09-14
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/clk-provider.h>
#include <linux/clocksource.h>
#include <linux/irqchip.h>
#include <linux/amba/sp810.h>
#include <linux/memblock.h>
#include <linux/delay.h>

#include <asm/mach/arch.h>
#include <asm/mach/map.h>
#include <mach/io_map.h>
#include <asm/cacheflush.h>

#include <linux/of_address.h>
#include <linux/of_fdt.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>

#include <mach/platsmp.h>

#include <mach/kexport.h>

#ifdef CONFIG_RTOS_RESET_NOTIFIER
#include <linux/hal/cache_ecc_check.h>
#endif

#ifdef CONFIG_RTOS_CPU_MODE
#include <linux/hal/hisilicon.h>
#endif

#ifdef CONFIG_RTOS_HAL_BUGFIX
#include <linux/hal/common.h>
#endif

#ifdef CONFIG_HISI_L3_OUTER_CACHE
#include <linux/hal/l3_cache.h>
#endif

#ifdef CONFIG_HISI_L2_OUTER_CACHE
#include <linux/hal/l2_cache.h>
#endif

#ifdef CONFIG_RTOS_CPU_PARAM
#include <linux/hal/cpu_param.h>
#endif

#include <linux/hal/watchdog.h>

#include <linux/hal/drol.h>

#ifdef CONFIG_RTOS_HAL_CACHE_L2X0
#include <asm/hardware/cache-l2x0.h>
#endif

#ifdef CONFIG_RTOS_KASLR
#include <asm/memory.h>
#endif

#ifdef CONFIG_DEBUG_LL
#include <mach/debug_ll.h>
static struct map_desc hisi_uart_desc;
static int __init map_uart_io(void)
{
	unsigned int uart_phys_addr = 0;

	uart_phys_addr = *((int *)(CONFIG_PAGE_OFFSET
#ifdef CONFIG_RANDOMIZE_BASE
				+ kaslr_offset()
#endif
				+ ARCH_HISI_DEBUG_BASE
				+ ARCH_HISI_UART_ADDR_OFFSET));
	hisi_uart_desc.pfn = __phys_to_pfn(uart_phys_addr);
#ifdef CONFIG_ARM_LPAE
	uart_phys_addr &= LPAE_PAGE_MASK;
#else
	uart_phys_addr &= NO_LPAE_PAGE_MASK;
#endif
	hisi_uart_desc.virtual = uart_phys_addr + ARCH_HISI_DEBUG_UART_VIRT;
	hisi_uart_desc.length = UART_IO_SIZE;
	hisi_uart_desc.type = MT_DEVICE;
	iotable_init(&hisi_uart_desc, 1);
	return 0;
}
#endif

void __init hisi_dt_map_io(void)
{
#ifdef CONFIG_DEBUG_LL
	map_uart_io();
#endif
#ifdef CONFIG_SMP
	hisi_dt_smp_map_io();
#endif
}

#ifdef CONFIG_RTOS_CPU_MODE
unsigned int max_nr_irqs;

static int arch_gic_and_cpu_param(struct device_node *node)
{
	if (of_property_read_u32(node, "max_nr_irqs", &max_nr_irqs)) {
		printk(KERN_WARNING DTS_GIC_WARN \
			"Find max_nr_irqs fail in gic node, we set 160 as default!\n");
#ifdef	CONFIG_CORTEX_A9
		max_nr_irqs = 160;
#else
		max_nr_irqs = 394;
#endif
	}

	return 0;
}

unsigned int gic_dist_base_addr;

static void __init hisi_gic_info_parse(void)
{
	struct device_node *node;

	node = of_find_compatible_node(NULL, NULL, "hisilicon,hip04-gic");
	if (!node) {
		printk(KERN_ERR DTS_GIC_ERR "Cannot find hisilicon,hip04-gic\n");
		return;
	}

	if (of_property_read_u32(node, "reg", &gic_dist_base_addr)) {
		printk(KERN_ERR DTS_GIC_ERR "Cannot find gic dist base addr\n");
		gic_dist_base_addr = 0;
	}

	if (arch_gic_and_cpu_param(node))
		printk(KERN_WARNING DTS_GIC_WARN \
			"Run arch_gic_and_cpu_param func fail, check what happen when analyziz cpu desc\n");
}
#endif

void __init hisi_dt_init_early(void)
{
#ifdef CONFIG_RTOS_CPU_MODE
	hisi_gic_info_parse();
#endif
#ifdef CONFIG_RTOS_CPU_PARAM
	arch_cpu_param_init();
#endif

#ifdef CONFIG_RTOS_HAL_CACHE_L2X0
	parse_l2x0_info();
#endif
}

#define SYSCTL_OPS_MAX 4
static struct drol_op sysctl_ops[SYSCTL_OPS_MAX];

static void __init hisi_dt_timer_init(void)
{
	int ret;

	ret = drol_do_op_once("arm,sysctl-init",
			sysctl_ops, SYSCTL_OPS_MAX);
	if (ret < 0) {
		pr_err("sysctl init %d failed\n", ret);
		return;
	} else if (ret == 0) {
		pr_info("can't need sysctl init\n");
	}

	of_clk_init(NULL);
	timer_probe();
}

static bool has_reboot_by_reg_info(void)
{
	struct device_node *np = NULL;

	np = of_find_compatible_node(NULL, NULL, "reboot,register");
	if (!np)
		return false;
	else
		return true;
}

static bool has_reboot_by_wtd_info(void)
{
	struct device_node *np = NULL;

	np = of_find_compatible_node(NULL, NULL, "reboot,cpu-wtd");
	if (!np)
		return false;
	else
		return true;
}

#define BOARD_GPIO_REBOOT	1
#define	BOARD_INNER_DOG_REBOOT	2

static bool is_inner_dog_reboot(void)
{
	unsigned int reboot_type = 1;
	struct device_node *np = NULL;

	np = of_find_compatible_node(NULL, NULL, "reboot,cpu-wtd");
	if (of_property_read_u32(np, "reboot_type", &reboot_type)) {
		pr_info("need use gpio reboot\n");
		return false;
	}

	if (reboot_type == BOARD_INNER_DOG_REBOOT)
		return true;
	return false;
}

static bool has_reboot_by_gpio_info(void)
{
	struct device_node *np = NULL;

	np = of_find_compatible_node(NULL, NULL, "reboot");
	if (!np)
		return false;
	else
		return true;
}

#define REBOOT_INIT_STAGE 0
#define REBOOT_PROC_STAGE 1

#define REBOOT_BY_REG_OPS_MAX 4
#define REBOOT_BY_WTD_OPS_MAX 8
#define REBOOT_BY_GPIO_OPS_MAX 8
static struct drol_op reboot_by_reg_ops[REBOOT_BY_REG_OPS_MAX];
static struct drol_op reboot_by_wtd_ops[REBOOT_BY_WTD_OPS_MAX];
static struct drol_op reboot_by_gpio_ops[REBOOT_BY_GPIO_OPS_MAX];

static int reboot_do_op_once(const char *name, struct drol_op *op, int op_size, int reboot_stage)
{
	int nr;
	int ret;

	nr = drol_get_op(op, op_size, name, NULL);
	if (nr < 0)
		return 0;

	if (reboot_stage == REBOOT_INIT_STAGE) {
		ret = drol_map_op(op, nr, DROL_IOREMAP, DROL_IOUNMAP);
		if (ret)
			return ret;
	} else {
		drol_do_op(op, nr);
	}

	return nr;
}

static void hisi_dt_handle(int reboot_stage)
{
	int ret;

	if (has_reboot_by_reg_info()) {
		ret = reboot_do_op_once("reboot,register",
				reboot_by_reg_ops, REBOOT_BY_REG_OPS_MAX, reboot_stage);
		if (ret == 0) {
			pr_err("cannot find the reboot register for reboot in dtb\n");
			return;
		} else if (ret < 0) {
			pr_err("reboot register ioremap %d failed\n", ret);
			return;
		}

		if (reboot_stage == REBOOT_PROC_STAGE)
			mdelay(1000);
	}

	if (has_reboot_by_wtd_info()) {
		if (is_inner_dog_reboot()) {
			ret = reboot_do_op_once("reboot,cpu-wtd-restart",
					reboot_by_wtd_ops, REBOOT_BY_WTD_OPS_MAX, reboot_stage);
			if (ret == 0) {
				pr_err("cannot find the inner dog for reboot in dtb\n");
				return;
			} else if (ret < 0) {
				pr_err("inner dog ioremap %d failed\n", ret);
				return;
			}

			if (reboot_stage == REBOOT_PROC_STAGE)
				mdelay(1000);
		}
	}

	if (has_reboot_by_gpio_info()) {
		ret = reboot_do_op_once("reboot,gpio-restart",
				reboot_by_gpio_ops, REBOOT_BY_GPIO_OPS_MAX, reboot_stage);
		if (ret == 0) {
			pr_err("cannot find reboot gpio for reboot in dtb\n");
			return;
		} else if (ret < 0) {
			pr_err("gpio ioremap %d failed\n", ret);
			return;
		}

		if (reboot_stage == REBOOT_PROC_STAGE)
			mdelay(1000);
	}
}

#define REBOOT_INIT_OPS_MAX 4
static struct drol_op reboot_init_ops[REBOOT_INIT_OPS_MAX];

static const struct of_device_id hisi_dt_bus_match[] __initconst = {
	{ .compatible = "arm,amba-bus", },
	{}
};

static void __init hisi_dt_init(void)
{
	int ret;

	of_platform_populate(NULL, hisi_dt_bus_match, NULL, NULL);

	if (has_reboot_by_gpio_info()) {
		ret = drol_do_op_once("reboot,gpio-init",
				reboot_init_ops, REBOOT_INIT_OPS_MAX);
		if (ret <= 0) {
			pr_err("reboot gpio init failed.\n");
			return;
		}
	}

	hisi_dt_handle(REBOOT_INIT_STAGE);
}

/*
 * DDR start area used for slave core boot up; the area layout is as below:
 * 0x0 ~ 0x5ff is used for code & data section (use low addr 0x0 fisrt);
 * 0x600 ~ 0x6ff is used for store slave core init info (use low addr 0x600 fisrt);
 * 0x700 ~ 0x7bf is used for non-sec feature;
 * 0x7c0 ~ 0x7ff is reserve for other HAL sub system features;
 * 0x800 ~ 0xdff is used for stack when slave core boot up calling C func (decrease stack, use high addr 0xdfc first);
 * 0xe00 ~ 0xfff is used for HAL sub system self-debug(use low addr 0xe00 first).
 * BTW: 0x1000~0x2fff is reserve for dtb, to compatible with prev version; 0x3000 ~ above, used for ori kernel.
 */
#define	HISI_MEM_RESERVE_SIZE	0x3000
#define HISI_1M_MASK		(~(0x100000UL - 1))

#ifdef CONFIG_RTOS_KASLR
#define CELL_SIZE      4

static int __init early_init_dt_scan_slave_boot_addr(unsigned long node, const char *uname,
						int depth, void *data)
{
	unsigned int slave_boot_phys_addr;
	const __be32 *prop = NULL;
	int ret = 0;
	int size = 0;

	if (depth != 1)
		return 0;

	prop  = of_get_flat_dt_prop(node, "slave_boot_addr", &size);
	if (!prop)
		return 0;

	slave_boot_phys_addr = of_read_number(prop, size / CELL_SIZE);
	if (!memblock_is_memory(slave_boot_phys_addr))
		return 0;
	ret = memblock_reserve(slave_boot_phys_addr, PAGE_SIZE);
	if (ret)
		pr_err("Error: hisi_mem_reserve slave boot addr fail when boot up!\n");
	return 0;
}
#endif

static void __init hisi_mem_reserve(void)
{
	int ret;

#ifdef CONFIG_RTOS_KASLR
	ret = memblock_reserve((virt_to_phys((void *)(uintptr_t)((CONFIG_PAGE_OFFSET + kaslr_offset()) & HISI_1M_MASK))),
				HISI_MEM_RESERVE_SIZE);
	if (ret)
		pr_err("Error: hisi_mem_reserve fail when boot up!\n");

	of_scan_flat_dt(early_init_dt_scan_slave_boot_addr, NULL);
#else
	ret = memblock_reserve((virt_to_phys((void *)(CONFIG_PAGE_OFFSET & HISI_1M_MASK))), HISI_MEM_RESERVE_SIZE);
#endif

	if (ret)
		pr_err("Error: hisi_mem_reserve fail when boot up!\n");
}

void hisi_common_restart(void)
{
#ifdef CONFIG_SMP
	unsigned int timeout;

	timeout = 5000;		/* wait for 5s */
	while (num_online_cpus() > 1 && timeout--)
		mdelay(1);

	if (num_online_cpus() > 1)
		pr_emerg("shutdown other cpu error");
#endif
	hwdog_stop();

#ifdef CONFIG_RTOS_REBOOT_FLUSH_BEFORE_DISABLE
	/* On 1380 platform, flush L1 cache after disable L2 cache may
	 * probably cause cache coherence problem. Move it before disable cache
	 */
	flush_cache_all();
#endif

#ifdef CONFIG_HISI_L2_OUTER_CACHE
	/* disable L2 cache here
	 * else L1 cache disabling will hang up
	 */
	l2cache_exit();
#endif

#ifdef CONFIG_HISI_L3_OUTER_CACHE
	/*
	 * Flush and disable l3 cache at here;
	 * When L3 cache is disable, flush L1\L2 cache data will flush into DDR.
	 */

	l3cache_exit();
#endif

#ifndef CONFIG_RTOS_REBOOT_FLUSH_BEFORE_DISABLE
	flush_cache_all();
#endif

#ifdef CONFIG_RTOS_RESET_NOTIFIER
	call_reset_notifiers();
#endif
}

void hisi_dt_restart(enum reboot_mode mode, const char *b)
{
	hisi_common_restart();

	hisi_dt_handle(REBOOT_PROC_STAGE);

	mdelay(2000);
	pr_err("reboot fail!");
}

static const char *const hisi_a9_dt_match[] __initconst = {
	"Hisilicon Cortex-A9",
	NULL,
};

DT_MACHINE_START(HISILICON_A9_DT, "Hisilicon A9")
	.dt_compat	= hisi_a9_dt_match,
	.smp		= smp_ops(hisi_a9_smp_ops),
	.map_io		= hisi_dt_map_io,
	.init_early	= hisi_dt_init_early,
	.init_irq	= irqchip_init,
	.init_time	= hisi_dt_timer_init,
	.init_machine	= hisi_dt_init,
	.reserve	= hisi_mem_reserve,
	.restart	= hisi_dt_restart
MACHINE_END

static const char *const hisi_a15_dt_match[] __initconst = {
	"Hisilicon Cortex-A15",
	NULL,
};

DT_MACHINE_START(HISILICON_A15_DT, "Hisilicon A15")
	.dt_compat	= hisi_a15_dt_match,
	.smp		= smp_ops(hisi_a15_smp_ops),
	.map_io		= hisi_dt_map_io,
	.init_early	= hisi_dt_init_early,
	.init_irq	= irqchip_init,
	.init_time	= hisi_dt_timer_init,
	.init_machine	= hisi_dt_init,
	.reserve	= hisi_mem_reserve,
	.restart	= hisi_dt_restart
MACHINE_END

static const char *const sd5203_compat[] __initconst = {
	"hisilicon,sd5203",
	NULL,
};

DT_MACHINE_START(SD5203, "Hisilicon SD5203 (Flattened Device Tree)")
	.dt_compat      = sd5203_compat,
	.map_io		= hisi_dt_map_io,
	.init_early	= hisi_dt_init_early,
	.init_machine	= hisi_dt_init,
	.reserve	= hisi_mem_reserve,
	.restart	= hisi_dt_restart
MACHINE_END
